package io.yunho.webflux.client.service;

import io.yunho.webflux.client.handlers.WebClientRestHandler;
import io.yunho.webflux.client.domain.MethodInfo;
import io.yunho.webflux.client.domain.ServerInfo;
import io.yunho.webflux.client.annotations.ApiServer;
import io.yunho.webflux.client.interfaces.ProxyCreator;
import io.yunho.webflux.client.interfaces.RestHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author: yunho
 * @date: 2021/5/14 16:07
 * @description: 使用jdk动态代理
 */
@Slf4j
public class JdkProxyCreator implements ProxyCreator {

    @Override
    public Object createProxy(Class<?> type) {
        log.info("createProxy*****"+type);

        //根据接口得到API信息
        ServerInfo serverInfo = extractServerInfo(type);
        log.info("serverInfo*****"+serverInfo);
        //给每一个代理类一个实现
        RestHandler restHandler = new WebClientRestHandler();
        //初始化服务器信息 初始化web client
        restHandler.init(serverInfo);
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{type}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //根据方法和参数得到调用信息
                        MethodInfo methodInfo = extractMethodInfo(method,args);
                        log.info("methodInfo*****"+methodInfo);
                        //调用rest
                        //这种写法 发现serverInfo是每次调用都会传，而且它是一样的，因此可以将它在类初始化时定义。
//                        return restHandler.invokeRest(serverInfo,methodInfo);
                        return restHandler.invokeRest(methodInfo);
                    }

                    /**
                     * 根据方法定义和调用参数得到相关信息
                     * @param method
                     * @param args
                     * @return
                     */
                    private MethodInfo extractMethodInfo(Method method, Object[] args) {

                        MethodInfo methodinfo = new MethodInfo();
                        extractHttpMethod(method, methodinfo);
                        extractRequestParams(method, args, methodinfo);
                        //提取返回对象信息
                        extractReturnInfo(method,methodinfo);
                        return methodinfo;
                    }

                    /**
                     * 提取返回对象信息
                     * @param method
                     * @param methodinfo
                     */
                    private void extractReturnInfo(Method method, MethodInfo methodinfo) {
                        //isAssignableFrom 判断是否某个类的子类，instanceof 判断实例
                        boolean assignableFrom = method.getReturnType().isAssignableFrom(Flux.class);
                        methodinfo.setIsFlux(assignableFrom);
                        //得到返回对象的实际类型
                        Class<?> elementType = extractElementType(method.getGenericReturnType());
                        methodinfo.setReturnElementType(elementType);
                    }

                    /**
                     * 得到泛型类型的实际类型
                     * @param genericReturnType
                     * @return
                     */
                    private Class<?> extractElementType(Type genericReturnType) {
                        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                        return (Class<?>) actualTypeArguments[0];
                    }

                    /**
                     * 获取请求参数
                     * @param method
                     * @param args
                     * @param methodinfo
                     */
                    private void extractRequestParams(Method method, Object[] args, MethodInfo methodinfo) {
                        //得到参数和body
                        Parameter[] parameters = method.getParameters();
                        Map<String,Object> params = new LinkedHashMap<>();
                        methodinfo.setParams(params);

                        for (int i = 0; i < parameters.length; i++) {
                            PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
                            if(pathVariable !=null){
                                params.put(pathVariable.value(), args[i]);
                            }
                            //是否带了requestBody
                            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
                            if(requestBody!=null){
                                methodinfo.setBody((Mono<?>) args[i]);
                                //请求对象的实际类型
                                methodinfo.setBodyClass(extractElementType(parameters[i].getParameterizedType()));
                            }

                        }
                    }

                    /**
                     * 获取url和httpmethod
                     * @param method
                     * @param methodinfo
                     */
                    private void extractHttpMethod(Method method, MethodInfo methodinfo) {
                        Annotation[] annotations = method.getAnnotations();
                        for (Annotation a:annotations
                             ) {
                            //GET
                            if(a instanceof GetMapping){
                                GetMapping getMapping = (GetMapping) a;
                                methodinfo.setUrl(getMapping.value()[0]);
                                methodinfo.setHttpMethod(HttpMethod.GET);
                            }//POST
                            else if(a instanceof PostMapping){
                                PostMapping m = (PostMapping) a;
                                methodinfo.setUrl(m.value()[0]);
                                methodinfo.setHttpMethod(HttpMethod.POST);
                            }//DELETE
                            else if(a instanceof DeleteMapping){
                                DeleteMapping m = (DeleteMapping) a;
                                methodinfo.setUrl(m.value()[0]);
                                methodinfo.setHttpMethod(HttpMethod.DELETE);
                            }
                        }
                    }
                });
    }

    /**
     * 返回服务器信息
     * @param type
     * @return
     */
    private ServerInfo extractServerInfo(Class<?> type) {
        ServerInfo serverinfo = new ServerInfo();
        ApiServer annotation = type.getAnnotation(ApiServer.class);
        serverinfo.setUrl(annotation.value());
        return serverinfo;
    }
}
